package com.mai4j.maiadmin.service.impl;

import com.mai4j.maiadmin.misc.DbUtils;
import com.mai4j.maiadmin.model.dto.CurrencyDTO;
import com.mai4j.maiadmin.model.dto.PageDTO;
import com.mai4j.maiadmin.model.http.datatables.Datatable;
import com.mai4j.maiadmin.model.http.datatables.Search;
import com.mai4j.maiadmin.service.CurrencyDescriptionService;
import com.mai4j.maiadmin.service.CurrencyService;
import com.mai4j.maiadmin.service.QueryService;
import com.mai4j.maiadmin.support.IdProvider;
import com.mai4j.maiadmin.support.JooqProvider;
import com.mai4j.maiadmin.support.cache.CacheName;
import org.apache.commons.lang3.StringUtils;
import org.jooq.*;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.mai4j.maiadmin.generated.Tables.CURRENCY;
import static com.mai4j.maiadmin.generated.Tables.CURRENCY_DESCRIPTION;

/**
 * 微信公众号请关注: 开源螺丝钉
 * <br>
 * 码云请关注: <a href="https://gitee.com/xiyoufang">https://gitee.com/xiyoufang</a>
 * <br>
 * 哔哩哔哩请关注: <a href="https://space.bilibili.com/438927834">https://space.bilibili.com/438927834</a>
 *
 * @author xiyoufang
 */
@Service
public class CurrencyServiceImpl implements CurrencyService {

    private final CurrencyDescriptionService currencyDescriptionService;


    private final QueryService queryService;

    public CurrencyServiceImpl(CurrencyDescriptionService currencyDescriptionService, QueryService queryService) {
        this.currencyDescriptionService = currencyDescriptionService;
        this.queryService = queryService;
    }

    @Cacheable(value = CacheName.DATA, key = "'currencies'")
    @Override
    public List<CurrencyDTO> list() {
        DSLContext dsl = JooqProvider.dsl();
        return dsl.select(CURRENCY.asterisk())
                .from(CURRENCY)
                .orderBy(CURRENCY.SORT_ORDER).fetch(recordCurrencyDTORecordMapper());

    }

    @Override
    public PageDTO<CurrencyDTO> paginate(Datatable datatable) {
        DSLContext dsl = JooqProvider.dsl();
        SelectJoinStep<Record> query = dsl.select(CURRENCY.asterisk()).from(CURRENCY);
        String value = Optional.ofNullable(datatable.getSearch()).orElse(new Search()).getValue();
        List<Condition> conditions = new ArrayList<>();
        if (StringUtils.isNotBlank(value)) {
            value = "%" + value + "%";
            conditions.add(
                    CURRENCY.CODE.like(value)
                            .or(CURRENCY.CURRENCY_ID.in(
                                    dsl.select(CURRENCY_DESCRIPTION.CURRENCY_ID)
                                            .from(CURRENCY_DESCRIPTION)
                                            .where(CURRENCY_DESCRIPTION.NAME.like(value)))));
        }
        query.where(conditions);
        int count = queryService.fetchCount(query).count;
        List<CurrencyDTO> currencyDTOS = count == 0 ? new ArrayList<>() : query
                .orderBy(CURRENCY.SORT_ORDER)
                .limit(datatable.getStart(), datatable.getLength())
                .fetch(recordCurrencyDTORecordMapper());
        return new PageDTO<>(currencyDTOS, DbUtils.getPage(datatable), datatable.getLength(), DbUtils.getTotalPage(count, datatable), count);
    }

    @Caching(evict = {
            @CacheEvict(value = CacheName.DATA, key = "'currencies'"),
            @CacheEvict(value = "data", key = "'currency:' + #currencyDTO.getCurrencyId()")})
    @Transactional
    @Override
    public CurrencyDTO saveOrUpdate(CurrencyDTO currencyDTO) {
        DSLContext dsl = JooqProvider.dsl();
        int count = dsl.fetchCount(dsl.selectFrom(CURRENCY)
                .where(CURRENCY.CURRENCY_ID.eq(currencyDTO.getCurrencyId())));
        long currencyId = count == 0 ? IdProvider.next() : currencyDTO.getCurrencyId();
        if (count == 0) {
            dsl.insertInto(CURRENCY)
                    .columns(CURRENCY.CURRENCY_ID,
                            CURRENCY.CODE,
                            CURRENCY.SYMBOL_LEFT,
                            CURRENCY.SYMBOL_RIGHT,
                            CURRENCY.DECIMAL_PLACE,
                            CURRENCY.VALUE,
                            CURRENCY.SORT_ORDER,
                            CURRENCY.STATUS,
                            CURRENCY.DATE_ADDED,
                            CURRENCY.DATE_MODIFIED
                    )
                    .values(currencyId,
                            currencyDTO.getCode(),
                            currencyDTO.getSymbolLeft(),
                            currencyDTO.getSymbolRight(),
                            currencyDTO.getDecimalPlace(),
                            currencyDTO.getValue(),
                            currencyDTO.getSortOrder(),
                            currencyDTO.getStatus(),
                            LocalDateTime.now(),
                            LocalDateTime.now())
                    .execute();
        } else {
            dsl.update(CURRENCY)
                    .set(CURRENCY.CODE, currencyDTO.getCode())
                    .set(CURRENCY.SYMBOL_LEFT, currencyDTO.getSymbolLeft())
                    .set(CURRENCY.SYMBOL_RIGHT, currencyDTO.getSymbolRight())
                    .set(CURRENCY.DECIMAL_PLACE, currencyDTO.getDecimalPlace())
                    .set(CURRENCY.VALUE, currencyDTO.getValue())
                    .set(CURRENCY.SORT_ORDER, currencyDTO.getSortOrder())
                    .set(CURRENCY.STATUS, currencyDTO.getStatus())
                    .set(CURRENCY.DATE_MODIFIED, LocalDateTime.now())
                    .where(CURRENCY.CURRENCY_ID.eq(currencyId))
                    .execute();
        }
        currencyDescriptionService.saveDescriptions(currencyId, currencyDTO.getDescriptions());
        return get(currencyId);
    }

    @Cacheable(value = "data", key = "'currency:' + #currencyId")
    @Override
    public CurrencyDTO get(Long currencyId) {
        return JooqProvider.dsl().select(CURRENCY.asterisk())
                .from(CURRENCY)
                .where(CURRENCY.CURRENCY_ID.eq(currencyId))
                .fetchOne(recordCurrencyDTORecordMapper());
    }

    @Caching(evict = {
            @CacheEvict(value = CacheName.DATA, key = "'currencies'"),
            @CacheEvict(value = CacheName.DATA, key = "'currency:' + #currencyId")})
    @Override
    public void updateValue(Long currencyId, BigDecimal value) {
        JooqProvider.dsl().update(CURRENCY)
                .set(CURRENCY.VALUE, value)
                .set(CURRENCY.DATE_MODIFIED, LocalDateTime.now())
                .where(CURRENCY.CURRENCY_ID.eq(currencyId))
                .execute();
    }

    @Transactional
    @Caching(evict = {
            @CacheEvict(value = CacheName.DATA, key = "'currencies'"),
            @CacheEvict(value = CacheName.DATA, key = "'currency:' + #currencyId")})
    @Override
    public void delete(Long currencyId) {
        JooqProvider.dsl()
                .deleteFrom(CURRENCY)
                .where(CURRENCY.CURRENCY_ID.eq(currencyId))
                .execute();
        currencyDescriptionService.delete(currencyId);
    }

    /**
     * RecordCurrencyDTORecordMapper
     *
     * @return RecordMapper
     */
    private RecordMapper<Record, CurrencyDTO> recordCurrencyDTORecordMapper() {
        return record -> {
            CurrencyDTO currencyDTO = record.into(CurrencyDTO.class);
            // 因为使用缓存，所以这种方式查询并不会增加数据库压力
            currencyDTO.setDescriptions(currencyDescriptionService.get(currencyDTO.getCurrencyId()));
            return currencyDTO;
        };
    }
}
